<?php

class Treeclass {

	/* tablename and field_definitions
	*
	*
	*
	*/

	protected $table	= '';
	protected $db		= null;
	protected $id_node_name = 'node';
	protected $fields	= array
	(
		'id'		=> 'name_of_the_filed_for_id',
		'parent_id'	=> 'name_of_the_filed_for_parent_id',
		'position'	=> 'name_of_the_filed_for_position_of_the_child',
		'left'	=> 'name_of_the_filed_for_position_of_the_child',
		'right'	=> 'name_of_the_filed_for_position_of_the_child',
		'level'	=> 'name_of_the_filed_for_position_of_the_child'
	);
	protected $str_fields = '';

	function __construct($params)
	{
		$this->table   = $params['table'];
		unset  ($params['table']);
		$this->id_node_name = $params['id_node_name'];
		unset  ($params['id_node_name']);
		$this->fields = $params;
		$this->str_fields = implode(",", $this->fields);
		//private $ci;
		$this->ci =& get_instance();
		$this->db = &$this->ci->db;

	}

	function check_root_node()
	{
		$this->db->where($this->fields['parent_id'], 0);
		$query = $this->db->get($this->table);
		if ($query->num_rows() == 1)
		{
			return true;
		}
		return false;
	}

	function create_root_node()
	{

		$data = array(
			$this->fields['parent_id'] => 0,
			$this->fields['position']  => 1,
			$this->fields['level']     => 1
        );
		$this->db->insert($this->table, $data);
		if ($this->db->affected_rows() == 1)
			return $this->db->insert_id();
		return false;
	}


	function set_data(&$id, &$data)
	{
		$this->db->where($this->fields['id'], $id);
		$this->db->update($this->table, $data);
		if ($this->db->affected_rows() == 1)
		{
			return true;
		}
		return false;
	}

	function fetch_data(&$id, $name_fields)
	{
		$this->db->where($this->fields['id'], $id);
		$this->db->select(implode(",", $name_fields));
		$query = $this->db->get($this->table);
		if ($row = $query->result_array())
		{
			return $row[0];
		}
		return false;
	}

	function count_childs($id)
	{
		$num_childs = 0;
		$sql = $this->db->query('SELECT count(*) as total_childs FROM '.$this->table.
				' WHERE '.$this->fields['parent_id'].'='.$id);
		//print_r ($this->db);
		if ($row = $sql->row_array())
		{
			$num_childs = $row['total_childs'];
		}
		return (int)$num_childs;
	}

	function append_node($id = 0, $position = 0)
	{
		$ret_id = false;
		$level    = $this->get_node_level($id);
		if ($position == 0)
		{
			$position = $this->count_childs($id) + 1;
		}
		$data = array(
			$this->fields['parent_id'] => $id,
			$this->fields['position']  => $position,
			$this->fields['level']     => $level + 1
        );
		$this->db->insert($this->table, $data);
		if ($this->db->affected_rows() == 1)
		{
			$ret_id = $this->db->insert_id();
		}
		return $ret_id;
	}

	function get_node_info($id, $extra_fields = '')
	{
		$this->db->where($this->fields['id'], $id);
		$fields = $extra_fields == ''?$this->str_fields:$this->str_fields.','.
				$extra_fields;
		$this->db->select($fields);
		$query = $this->db->get($this->table);
		if ($row = $query->result_array())
		{
			return $row[0];
		}
		return false;
	}

	function insert_node_after($id)
	{
		$ret_id = false;
		$node = $this->get_node_info ($id);
		if ($node)
		{
			$query = $this->db->query('UPDATE '.$this->table.'
				SET '.$this->fields['position'].'='.$this->fields['position'].'+1
				WHERE '.$this->fields['parent_id'].'='.$node[$this->fields['parent_id']].'
					AND '.$this->fields['position'].'>'.$node[$this->fields['position']]);
			$ret_id = $this->append_node($node[$this->fields['parent_id']], $node[$this->fields['position']] + 1);
		}
		return $ret_id;
	}

	function insert_node_before($id)
	{
		$ret_id = false;
		$node = $this->get_node_info ($id);
		if ($node)
		{
			$query = $this->db->query('UPDATE '.$this->table.'
				SET '.$this->fields['position'].'='.$this->fields['position'].'+1
				WHERE '.$this->fields['parent_id'].'='.$node[$this->fields['parent_id']].'
					AND '.$this->fields['position'].'>='.$node[$this->fields['position']]);
			$ret_id = $this->append_node($node[$this->fields['parent_id']], $node[$this->fields['position']]);
		}
		return $ret_id;
	}

	// получить все ID для парента
	// если $whole_branch = true - получить ID для всей ветки дерева
	function fetch_children_ids($id, $whole_branch = false)
	{
		$ids = array ();
		$this->db->where($this->fields['parent_id'], $id);
		$query = $this->db->get($this->table);
		foreach ($query->result_array() as $row)
		{
			$ids[]=$row[$this->fields['id']];
			if ($whole_branch == true && $this->count_childs($row[$this->fields['id']])>0)
			{
				$tmp = $this->fetch_children_ids($row[$this->fields['id']], $whole_branch);
				$ids = array_merge ($ids, $tmp);
			}
		}
		return $ids;
	}

	// удаляем узел в дереве
	// если $delete_childs = true - удаляем все дочерние узлы и ветки
	function delete_node($id, $delete_childs = false)
	{
		//return false;
		$ret = false;
		$node = $this->get_node_info ($id);
		// empty node and root node can not be deleted
		if ($node) // && $node[$this->fields['parent_id']] > 0)
		{
			$total_childs = $this->count_childs($id);
			if ($delete_childs == true || $total_childs == 0)
			{
				if ($total_childs >0 && $delete_childs == true)
				{
					$ids = $this->fetch_children_ids($id, true);
					foreach ($ids as $cid)
					{
						$this->db->delete($this->table, array($this->fields[$this->fields['id']] => $cid));
					}
				}
				$this->db->delete($this->table, array($this->fields[$this->fields['id']] => $id));
				if ($this->db->affected_rows() == 1)
				{
					$query = $this->db->query('UPDATE '.$this->table.'
						SET '.$this->fields['position'].'='.$this->fields['position'].'-1
						WHERE '.$this->fields['parent_id'].'='.$node[$this->fields['parent_id']].'
						AND '.$this->fields['position'].'>'.$node[$this->fields['position']].'
						ORDER BY '.$this->fields['position'].' ASC');
						$ret = true;
				}
			}
		}
		return $ret;
	}

	function move_node($id, $parent_id, $new_position, $copy)
	{
		$node = $this->get_node_info ($id);
		$pnode= $this->get_node_info ($parent_id);

		// проверяем существуют ли узлы
		//if ($node == false || $pnode == false) return false;
		if ($node == false) return false;

		// высбождаем в дереве место под перемещение нового элемента
		// в родительском узле
		$query = $this->db->query('UPDATE '.$this->table.'
			SET '.$this->fields['position'].'='.$this->fields['position'].'+1
			WHERE '.$this->fields['parent_id'].'='.$parent_id.'
			AND '.$this->fields['position'].'>'.$new_position.' ORDER BY '.$this->fields['position'].' DESC');
		// так как позиция - индекс - делаем дополнительный запрос
		$query = $this->db->query('UPDATE '.$this->table.'
			SET '.$this->fields['position'].'='.$this->fields['position'].'+1
			WHERE '.$this->fields['parent_id'].'='.$parent_id.'
			AND '.$this->fields['position'].'='.$new_position);

		if ($copy == true)
		{
			// делаем копию узла и задаем ему новую позицию и парент
/*
			$this->db->where($this->fields['id'], $id);
			$query = $this->db->get($this->table);
			if ($row = $query->result_array())
			{
				unset ($row[0][$this->fields['id']]);
				$row[0][$this->fields['parent_id']] = $parent_id;
				$row[0][$this->fields['position']] = $new_position;
				$this->db->insert($this->table, $row[0]);
				if ($this->db->affected_rows() == 1)
				{
					$id = $this->db->insert_id();
				}
			}
*/
		}
		else
		{
			// для перемещаемого узла задаем новый парент узел и новую позицию

			$level = $this->get_node_level($parent_id) + 1;

			$query = $this->db->query('UPDATE '.$this->table.'
				SET '.$this->fields['position'].'='.$new_position.',
					'.$this->fields['parent_id'].'='.$parent_id.',
					'.$this->fields['level'].'='.$level.'
				WHERE '.$this->fields['id'].'='.$node[$this->fields['id']]);
			// сдвигаем позиции в узле относительно той которая перемещена НЕ СКОПИРОВАНА
			$query = $this->db->query('UPDATE '.$this->table.'
				SET '.$this->fields['position'].'='.$this->fields['position'].'-1
				WHERE '.$this->fields['parent_id'].'='.$node[$this->fields['parent_id']].'
				AND '.$this->fields['position'].'>'.$node[$this->fields['position']].' ORDER BY '.$this->fields['position'].' ASC');


		}

		return $id;

	}


	function get_node_level($id)
	{
		$level = 0;
		while ($node = $this->get_node_info ($id))
		{
			$id = $node[$this->fields['parent_id']];
			$level += 1;
		}
		return $level;
	}


	function get_node_position ($id)
	{
		$position  = 0;
		$node = $this->get_node_info ($id);
		return $node[$this->fields['position']];
	}

	function get_node_parent ($id)
	{
		$position  = 0;
		$node = $this->get_node_info ($id);
		return $node[$this->fields['parent_id']];
	}

	function _dump_child(&$id, &$parent_id, &$name_fields, &$data, &$recuring)
	{
		$this->db->where($this->fields['parent_id'], $id);
		$this->db->order_by($this->fields['position']);
		$this->db->select(implode(",", $name_fields).','.$this->str_fields);
		$query = $this->db->get($this->table);
		if ($query->num_rows()>0)
		{

			foreach ($query->result_array() as $row)
			{
				$numberChilds = $this->count_childs($row['id']);
				if ($recuring == true)
				{
					$this->_dump_child($row[$this->fields['id']], $parent_id, $name_fields, $row, $recuring);
				}
				else
				{
					$row['num_childs'] = $numberChilds;
				}
				if ($row[$this->fields['parent_id']] > 0 && $parent_id != $row[$this->fields['parent_id']])
					$data['children'][] = $row;
				else
					$data[] = $row;
			}
		}
	}

	function _dump($id, &$name_fields, $recuring = false)
	{
		$data = array ();
		$this->_dump_child($id, $id, $name_fields, $data, $recuring);
		return $data;
	}

	function _to_json_loop ($node, &$result)
	{
		$tmp = array(
			"attr" => array("id" => $this->id_node_name."_".$node[$this->fields['id']], "rel"=>"folder"),
			"data" => $node['name'],
			"state" => ""
		);
		if (isset($node['children'])) {
			$tmp["state"] = "closed";
			foreach($node['children'] as $cid=>$cnode)
			{
				$ctmp = array(
					"attr" => array("id" => $this->id_node_name."_".$cnode[$this->fields['id']]),
					"data" => $cnode['name'],
					"state" => ""
				);
				if (isset($cnode['children']) || (isset($cnode['num_childs']) && $cnode['num_childs'] > 0) )
				{
					$ctmp["state"] = "closed";
				}
				$tmp['children'][] = $ctmp;
				if (isset($cnode['children']))
				{
					$this->_to_json_loop ($cnode, $tmp);
				}
			}
		}
		elseif (isset($node['num_childs']) && $node['num_childs'] > 0)
		{
			$tmp["state"] = "closed";
		}
		$result[] = $tmp;
	}

	function _to_json ($id, &$name_fields, $recuring = false)
	{


		$data =	$this->_dump($id, $name_fields, $recuring);


		$result = array();
		foreach($data as $id=>$node)
		{
			$this->_to_json_loop ($node, $result);
		}
		return json_encode ($result);
	}





}

?>